/*
 * Purpose: Defines the Variation of a JTextPane
 * to support source code pretty print display
 */
package View;

import Model.Error;
import Model.Programmable;
import Util.KeyAdapter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.border.EtchedBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

/**
 *
 * @author jase
 */
class SourceCodePane extends JPanel implements Programmable {

	private final JTextPane textPane;
	private final JTextArea message;
	private final JLabel info;
	private final Watcher watcher;
	private final String NEWLINE = System.getProperty("line.separator");
	private final UndoManager undoManager;
	private final UndoAction undoAction;
	private final RedoAction redoAction;

	public SourceCodePane() {
		undoManager = new UndoManager();
		undoAction = new UndoAction();
		redoAction = new RedoAction();
//		JMenuItem undoItem = new JMenuItem(undoAction);
//		JMenuItem redoItem = new JMenuItem(redoAction);
//
//		undoItem.setAccelerator(KeyStroke.getKeyStroke("ctrl Z"));
//		redoItem.setAccelerator(KeyStroke.getKeyStroke("ctrl Y"));

		setPreferredSize(new Dimension(800, 700));
		setBackground(Color.lightGray);

		watcher = new Watcher();
		watcher.start();

		setLayout(new BorderLayout());
		message = new JTextArea(2, 40);
		message.setLineWrap(true);
		message.setEditable(false);
		message.setEnabled(false);
		info = new JLabel();
		info.setPreferredSize(new Dimension(800, 20));
		info.setHorizontalAlignment(JLabel.RIGHT);
		message.setFont(
			new Font(Font.SERIF, Font.PLAIN, 28));
		message.setBorder(new EtchedBorder());
		info.setFont(
			new Font(Font.DIALOG, Font.ITALIC, 18));
		textPane = new JTextPane();


		JScrollPane scroll = new JScrollPane();
		scroll.setViewportView(textPane);
		add(scroll, BorderLayout.CENTER);
		add(message, BorderLayout.NORTH);
		add(info, BorderLayout.SOUTH);
		textPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 24));
	}

	public void activateBehaviour() {
		textPane.getDocument().addUndoableEditListener(new UndoableEditListener() {

			public void undoableEditHappened(UndoableEditEvent e) {
				System.out.println("undoableEditHappened!");
				undoManager.addEdit(e.getEdit());
				// TODO: add in undo and redo actions
				// see: http://java.sun.com/docs/books/tutorial/uiswing/components/generaltext.html#undo

				undoAction.updateUndoState();
				redoAction.updateRedoState();

			}
		});

		textPane.addCaretListener(new CaretListener() {

			public void caretUpdate(CaretEvent e) {
				info.setText("Line: " + computeLineNumber());
			}
		});

		textPane.addKeyListener(new KeyAdapter() {

			@Override
			public void keyTyped(KeyEvent e) {
//				System.out.println("mod: " + (e.getModifiers() == InputEvent.CTRL_MASK));

//				System.out.println("char: " + (int)e.getKeyChar());

				if (e.getKeyChar() == 26) { // control-Z
					undoAction.actionPerformed(null);
				} else if (e.getKeyChar() == 25) { // control-Y
					redoAction.actionPerformed(null);
				}
				watcher.changed();
			}

			@Override
			public void keyReleased(KeyEvent e) {
				info.setText("Line: " + computeLineNumber());
			}
		});
	}

	public void shutdown() {
		watcher.shutdown();
	}

	private int computeLineNumber() {
//		System.out.println("Compute line number: ");
		// the position within the text must be found by computing the lines
		String text = null;
		int caretPos = textPane.getCaretPosition();
//		System.out.println("caret: " + caretPos);
		text = textPane.getText();

		String[] lines = text.split(NEWLINE);
//		System.out.println("lines: " + lines.length);

		int count = 0;
		int pos = 0;
		for (String line : lines) {
//			System.out.println("line: " + line + " pos: " + pos);
			if (caretPos < pos) {
				break;
			}
			++count;
			pos += line.length() + 1;
		}

		return count;
	}

	public void setText(String text) {
		synchronized (textPane) {
			textPane.setText(text);
		}
	}

	public boolean codeChanged() {
		return watcher.isChanged();
	}

	public boolean readyToSave() {
		return watcher.isReady();
	}

	public void updated() {
		watcher.reset();
	}

	public String getCode() {
		String result = null;
		synchronized (textPane) {
			result = textPane.getText();
		}
		return result;
	}

	private void setMessage(String str) {
		message.setText(str); // just using word wrap TRUE - for the this iteration :)
	}

	public void error(Error info) {
		if (info.equals(Error.BLANK)) {
			setMessage("ready...");
		} else if (info.equals(Error.NONE)) {
			setMessage("okay!");
		} else if (info.equals(Error.UNKNOWN)) {
			setMessage("unknown error in this file");
		} else {
			setMessage(info.getMessage() + " near line: " + info.getLineNumber());
		}
	}

	// this is based on http://java.sun.com/docs/books/tutorial/uiswing/examples/
	// components/TextComponentDemoProject/src/components/TextComponentDemo.java
	class UndoAction extends AbstractAction {

		public UndoAction() {
			super("Undo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				System.out.println("undo action");
				undoManager.undo();
			} catch (CannotUndoException ex) {
//				System.out.println("Unable to undo: " + ex);
//				ex.printStackTrace();
			}
			updateUndoState();
			redoAction.updateRedoState();
		}

		protected void updateUndoState() {
			if (undoManager.canUndo()) {
				setEnabled(true);
				putValue("Name", undoManager.getUndoPresentationName());
			} else {
				setEnabled(false);
				putValue("Name", "Undo");
			}
		}
	}

	class RedoAction extends AbstractAction {

		public RedoAction() {
			super("Redo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				System.out.println("undo action");
				undoManager.redo();
			} catch (CannotRedoException ex) {
//				System.out.println("Unable to redo: " + ex);
//				ex.printStackTrace();
			}
			updateRedoState();
			undoAction.updateUndoState();
		}

		protected void updateRedoState() {
			if (undoManager.canRedo()) {
				setEnabled(true);
				putValue("Name", undoManager.getRedoPresentationName());
			} else {
				setEnabled(false);
				putValue("Name", "Redo");
			}
		}
	}

	// The watcher is used to run a typing updater watcher
	// if the user types a bunch of text, and then paused for about 1sec
	// the source code pane tells the program that it needs to be saved to file
	private class Watcher extends Thread {

		private final static int IDLE = 0;
		private final static int WAIT = 1;
		private final static int SAVE = 2;
		private int state = IDLE;
		private boolean running = true;
		private boolean ready = true; // force the source code to be compiled immediately
		private boolean changed = false;
		private int counter = 0;

		private void changed() {
			changed = true;
		}

		public void shutdown() {
			running = false;
		}

		public boolean isReady() {
			return ready;
		}

		public boolean isChanged() {
			return changed;
		}

		public void reset() {
			ready = false;
		}

		@Override
		public void run() {
			while (running) {
//				System.out.println("state: " + state + " ready: " + ready + " changed: " + changed + " counter: " + counter);
				switch (state) {
					case IDLE:
						if (!ready && changed) {
							state = WAIT;
							counter = 0;
							changed = false;
						}
						break;
					case WAIT:
						if (changed) {
							counter = 0;
							changed = false;
						}
						++counter;
						if (counter > 9) {
							state = SAVE;
						}
						break;
					case SAVE:
						counter = 0;
						ready = true;
						changed = false;
						state = IDLE;
						break;
				}

				try {
					Thread.sleep(10);
				} catch (Throwable t) {
				}
			}
		}
	}
}

